﻿using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.AspNet.SignalR;

namespace Synx.SignalR
{
    public interface IKeyValueStore
    {
        dynamic GetValue(string key);
        IEnumerable<dynamic> GetAllValues();
        void PutValue(string key, dynamic value);
        void RemoveValue(string key);
    }

    public class BasicKeyValueStore : IKeyValueStore
    {
        private readonly Dictionary<string, object> _dictionary = new Dictionary<string, object>();
        public dynamic GetValue(string key)
        {
            if (_dictionary.ContainsKey(key))
                return _dictionary[key];
            return null;
        }

        public IEnumerable<dynamic> GetAllValues()
        {
            return _dictionary.Values;
        }

        public void PutValue(string key, dynamic value)
        {
            _dictionary[key] = value;
        }



        public void RemoveValue(string key)
        {
            if (_dictionary.ContainsKey(key))
                _dictionary.Remove(key);
        }
    }

    public interface IGroupStorage
    {
        IKeyValueStore GetGroup(string key);
        void RemoveGroup(string key);
    }

    public class GroupStorage : IGroupStorage
    {
        public IDictionary<string, IKeyValueStore> groupStores = new Dictionary<string, IKeyValueStore>();
        public IKeyValueStore GetGroup(string key)
        {
            if (!groupStores.ContainsKey(key))
            {
                groupStores.Add(key, new BasicKeyValueStore());
            }
            return groupStores[key];
        }


        public void RemoveGroup(string key)
        {
            if (groupStores.ContainsKey(key))
            {
                groupStores.Remove(key);
            }
        }
    }

    public class Field
    {
        public string fieldId { get; set; }
        public string value { get; set; }
        public int revision { get; set; }
        public int lockrevision { get; set; }
        public int hoverrevision { get; set; }
        public bool isLocked { get; set; }
        public bool isHovered { get; set; }
        public string lockedBy { get; set; }

    }

    public class Synx : Hub
    {
        private readonly static IGroupStorage _revisionStore = new GroupStorage();
        private readonly static IKeyValueStore _groupTracker = new BasicKeyValueStore();

        public override System.Threading.Tasks.Task OnDisconnected()
        {
            var group = _groupTracker.GetValue(Context.ConnectionId + "_Group");
            _groupTracker.RemoveValue(Context.ConnectionId + "_Group");

            if (group != null)
            {
                lock (_groupTracker)
                {
                    var clientList = _groupTracker.GetValue(group + "_Clients");

                    if (clientList != null && clientList.Contains(Context.ConnectionId))
                    {
                        clientList.Remove(Context.ConnectionId);
                    }

                    if (clientList.Count > 0)
                    {
                        _groupTracker.PutValue(group + "_Clients", clientList);
                    }
                    else
                    {
                        _groupTracker.RemoveValue(group + "_Clients");
                        _revisionStore.RemoveGroup(group);
                    }


                }

                lock (_revisionStore)
                {
                    foreach (dynamic revision in _revisionStore.GetGroup(group).GetAllValues())
                    {
                        if (revision.isLocked
                            && revision.lockedBy == Context.ConnectionId)
                        {
                            revision.isLocked = false;
                            revision.isLocked = null;
                            revision.lockrevision++;
                            UnLock(revision.fieldId, revision.lockrevision);
                        }
                    }
                }
            }

            return base.OnDisconnected();
        }

        public void Register(string modelId)
        {
            //TODO authenticate user here before adding to group
            Groups.Add(Context.ConnectionId, modelId);

            lock (_groupTracker)
            {
                _groupTracker.PutValue(Context.ConnectionId + "_Group", modelId);
                var clientList = _groupTracker.GetValue(modelId + "_Clients") ?? new List<string>();

                if (!clientList.Contains(Context.ConnectionId))
                {
                    clientList.Add(Context.ConnectionId);
                }

                _groupTracker.PutValue(modelId + "_Clients", clientList);
            }

            var currentRevisions = _revisionStore.GetGroup(modelId).GetAllValues();
            if (currentRevisions != null && currentRevisions.Any())
            {
                Clients.Caller.UpdateServer();
                Clients.Caller.BatchUpdate(currentRevisions);
            }

            //TODO: send current locks
        }

        public void Lock(string fieldId, int lockrevision)
        {
            var group = _groupTracker.GetValue(Context.ConnectionId + "_Group");
            if (group != null)
            {
                lock (_revisionStore)
                {
                    var groupRevisionStore = _revisionStore.GetGroup(group);
                    var field = groupRevisionStore.GetValue(fieldId);

                    if (field == null)
                    {
                        field = new Field()
                        {
                            fieldId = fieldId,
                            revision = -1,
                            lockrevision = -1,
                            hoverrevision = -1

                        };
                        groupRevisionStore.PutValue(fieldId, field);
                    }

                    if (field.lockrevision < lockrevision)
                    {

                        field.isLocked = true;
                        field.lockedBy = Context.ConnectionId;
                        field.lockrevision = lockrevision;

                        Clients.OthersInGroup(group).Lock(fieldId, Context.ConnectionId, lockrevision);
                    }
                }
            }
        }

        public void SendChat(string message)
        {
            Clients.Others.AcceptChat(message, Context.ConnectionId);
        }

        public void UnLock(string fieldId, int lockrevision)
        {
            var group = _groupTracker.GetValue(Context.ConnectionId + "_Group");
            if (group != null)
            {
                lock (_revisionStore)
                {
                    var groupRevisionStore = _revisionStore.GetGroup(group);
                    var field = groupRevisionStore.GetValue(fieldId);

                    if (field == null)
                    {
                        field = new Field()
                        {
                            fieldId = fieldId,
                            revision = -1,
                            lockrevision = -1,
                            hoverrevision = -1
                        };
                        groupRevisionStore.PutValue(fieldId, field);
                    }


                    if (field.lockrevision < lockrevision)
                    {
                        if (field != null)
                        {
                            field.isLocked = false;
                            field.lockrevision = lockrevision;
                            field.lockedBy = null;
                        }

                        Clients.OthersInGroup(group).UnLock(fieldId, lockrevision);
                    }
                }
            }
        }

        public void Hover(string fieldId, int hoverrevision)
        {
            var group = _groupTracker.GetValue(Context.ConnectionId + "_Group");
            if (group != null)
            {
                lock (_revisionStore)
                {
                    var groupRevisionStore = _revisionStore.GetGroup(group);
                    var field = groupRevisionStore.GetValue(fieldId);

                    if (field == null)
                    {
                        field = new Field()
                                {
                                    fieldId = fieldId,
                                    revision = -1,
                                    lockrevision = -1,
                                    hoverrevision = -1
                                };
                        groupRevisionStore.PutValue(fieldId, field);
                    }

                    if (field.hoverrevision < hoverrevision)
                    {
                        field.hoverrevision = hoverrevision;
                        field.isHovered = true;
                        Clients.OthersInGroup(group).Hover(fieldId, hoverrevision);
                    }
                }
            }
        }

        public void Leave(string fieldId, int hoverrevision)
        {
            var group = _groupTracker.GetValue(Context.ConnectionId + "_Group");
            if (group != null)
            {
                lock (_revisionStore)
                {
                    var groupRevisionStore = _revisionStore.GetGroup(group);
                    var field = groupRevisionStore.GetValue(fieldId);

                    if (field == null)
                    {
                        field = new Field()
                        {
                            fieldId = fieldId,
                            revision = -1,
                            lockrevision = -1,
                            hoverrevision = -1
                        };
                        groupRevisionStore.PutValue(fieldId, field);
                    }

                    if (field.hoverrevision < hoverrevision)
                    {
                        field.hoverrevision = hoverrevision;
                        field.isHovered = false;
                        Clients.OthersInGroup(group).Leave(fieldId, hoverrevision);
                    }
                }
            }
        }

        public void Bokeh(string target, int top, int left, int size, int r, int g, int b)
        {
            Clients.Others.Bokeh(target, top, left, size, r, g, b);
        }

        public void Update(string fieldId, string value, int revision)
        {
            var group = _groupTracker.GetValue(Context.ConnectionId + "_Group");
            if (group != null)
            {
                lock (_revisionStore)
                {
                    var current = _revisionStore.GetGroup(group).GetValue(fieldId);
                    if (current == null || current.revision < revision)
                    {
                        _revisionStore.GetGroup(group).PutValue(fieldId, new Field()
                                                                         {
                                                                             fieldId = fieldId,
                                                                             value = value,
                                                                             revision = revision,
                                                                             hoverrevision = 0,
                                                                             lockrevision = 0,
                                                                             isLocked = false
                                                                         });
                    }
                    else
                    {
                        Clients.Caller.Resolve(fieldId, value, current.value, current.revision);
                    }
                }

                Clients.OthersInGroup(group).Update(fieldId, value, revision);
            }
        }
    }
}